package cmcc.dbi.execUtil;

import cmcc.dbi.dto.ShellQueryDTO;
import cmcc.dbi.exception.ExecutEngineExcption;
import com.jcraft.jsch.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

@Slf4j
@Data
/*
* 用来支持数据库主机的shell执行的jsch连接
* */
public class exeJschUtil {
    //@Value("${ssh.strictHostKeyChecking}")
    private String strictHostKeyChecking = "no";
    //@Value("${ssh.timeout}")
    private Integer timeout = 30000;
    //@Value("${execute.localDownPath}")
    private String localDownPath;
    //@Value("${ssh.Port}")
    private Integer jschPort = 22;

    // 设置上传或下载最大重试次数
    private int maxRetries = 3;

    private Session session;
    private Channel channel;
    private ChannelExec channelExec;
    private ChannelSftp channelSftp;
    private ChannelShell channelShell;

    //当前机器 -- 包含数据库主机的： 资源池，ip，port
    private ShellQueryDTO shellHost;

    //构造方法
    public exeJschUtil(ShellQueryDTO shellQueryDTO) {
        this.shellHost = shellQueryDTO;
    }

    /**
     * 根据给定的执行主机新建jsch连接
     *
     * @throws JSchException JSch异常
     */
    public void getConnect() throws JSchException {
        getConnect(shellHost);
    }

    /**
     * 根据给定的执行主机新建jsch连接
     *
     * @throws JSchException JSch异常
     */
    private void getConnect(ShellQueryDTO shellHost) throws JSchException {
        boolean connected = false;
        int retryCount = 0;
        if (session != null && session.isConnected()) {
            //连接已存在
            connected = true;
            log.info("ssh连接已建立");
            return;
        }
        //多次建立连接
        while(!connected && retryCount < maxRetries){
            log.info("主机连接信息：" +  shellHost.getHostIp()+"--" + shellHost.getHostUser()+"--" + shellHost.getHostPass()  + " 建立ssh连接失败！当前重试次数：" + retryCount);
            try {
                //新建jsch连接
                JSch jsch = new JSch();
                session = jsch.getSession(shellHost.getHostUser(), shellHost.getHostIp(), jschPort);
                //解密密码
                String realPass = AESEncryptionUtil.decrypt(shellHost.getHostPass());
                session.setPassword(realPass);
                Properties sshConfig = new Properties();
                sshConfig.put("StrictHostKeyChecking", strictHostKeyChecking);
                session.setConfig(sshConfig);
                session.connect(timeout);       //此处的timeout已经是jsch自动的断连重连，该时间限制了断连重连的最大时间
                log.info("数据库主机" + shellHost.getHostIp() + " 建立ssh连接成功！");
                connected = true;
            } catch (JSchException e) {
                retryCount ++ ;
                log.info("当前连接失败："+ e);
            }
        }
        if (!connected) {
            log.error("创建连接失败，超出最大重试次数: {}", maxRetries);
            //抛出此异常时已经多次尝试重连，结果还是失败，只能抛出异常，等待调用处处理---大概率是指定执行主机宕机
            throw new ExecutEngineExcption("数据库主机 "+  shellHost.getHostIp()  + "建立ssh连接失败！");
        }
    }


    /**
     * 收集并返回指令执行结果。
     *
     * @param input 输入流，包含指令执行的结果
     * @return 包含执行结果的字符串列表
     */
    private List<String> collectResult(InputStream input) throws IOException {
        List<String> resultLines = new ArrayList<>();
        // 使用try-with-resource语句自动关闭资源
        try (BufferedReader inputReader = new BufferedReader(new InputStreamReader(input))) {
            String inputLine;
            while ((inputLine = inputReader.readLine()) != null) {
                // 打印每行结果，可以根据需要选择是否保留此行
                //log.info("cmd执行输出流/错误信息流："+inputLine);
                resultLines.add(inputLine);
            }
        } catch (IOException e) {
            // 此处因为在调用时间已经检查过input不为空，所以不会报nullpointerException
            log.error("Error reading input stream", e);
            throw new IOException("读取inputStream出错："+e);
        }
        return resultLines;
    }

    /**
     * 未使用：正常执行命令
     * 逻辑：执行命令，如果失败重连，并执行该命令，超过最大尝试次数就抛出异常
     */

    public String exectueNormalCmd(String command) throws ExecutEngineExcption {
        InputStream cmdOutStream;
        List<String> results = null;

        boolean exectued = false;
        int retryCount = 0;

        while (!exectued && retryCount < maxRetries) {
            //在执行命令失败时，尝试重连并重新执行命令
            try {
                if (!session.isConnected()) {
                    //断连重连
                    log.error("建立执行命令通道时断连，重新建立ssh连接");
                    getConnect(shellHost);
                }
                channel = session.openChannel("exec");
                channelExec = (ChannelExec) channel;
                log.info("execCmd command - > {}", command);
                channelExec.setCommand(command);
                channel.setInputStream(null);
                channelExec.setErrStream(System.err);
                channel.connect();
                //获取输出流和错误信息流
                cmdOutStream = channelExec.getInputStream();
                InputStream errStream = channelExec.getErrStream();
                //获取结果
                results = collectResult(cmdOutStream);
                if(results.isEmpty()){
                    log.info("ExeCmd输出流为空，获取错误信息流");
                    results = collectResult(errStream);
                    if(results.isEmpty()){
                        log.info("错误信息流仍未空，结果赋值为NULL");
                        results.add("NULL");
                    }
                }
                exectued = true;
            } catch (Exception e) {
                log.error("执行命令失败，异常信息：{}，尝试重新连接... 重试次数: {}", e,retryCount + 1, e);
                retryCount++;
            }
        }
        if (!exectued) {
            log.error("执行命令 {} 失败，超出最大重试次数: {}",command, maxRetries);
            //抛出此异常时已经多次尝试重连，结果还是失败，只能抛出异常，等待调用处处理---大概率是指定执行主机宕机
            throw new ExecutEngineExcption("执行命令失败");
        }

        // 使用StringBuilder来构建没有中括号的字符串，因为直接results.toString()会把结果带中括号
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < results.size(); i++) {
            sb.append(results.get(i));
            if (i < results.size() - 1) {
                sb.append("; ");
            }
        }
        return sb.toString();
    }


    /**
     * 释放所有Channel资源
     */
    private void closeChannel() {
        if (channelSftp != null && channelSftp.isConnected()) {
            channelSftp.disconnect();
        }
        if (channelExec != null && channelExec.isConnected()) {
            channelExec.disconnect();
        }
        if (channel != null && channel.isConnected()) {
            channel.disconnect();
        }
    }

    /**
     * 释放Session资源
     */
    public void closeAll() {
        closeChannel();
        if(session != null && session.isConnected()){
            session.disconnect();
        }
    }
}